package cn.janescott.inner.space.lib.boot.listener;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration;
import com.alibaba.dubbo.spring.boot.bean.ClassIdBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;

import java.lang.reflect.Field;

/**
 * 此监听器是在CommandLineRunner被调用之前执行的
 *
 * @author scott
 * @date 2018/5/15
 * @see com.alibaba.dubbo.config.annotation.Reference
 * 引用的消费者在抽象类和一些其他情况下会出现null的情况
 * 所以使用监听器，在应用启动之后，对所有的@Reference的属性进行空值检查
 */
public class ApplicationStartedListener implements
        ApplicationListener<ApplicationStartedEvent>, ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(ApplicationStartedListener.class);

    private ApplicationContext applicationContext;

    /**
     * 参考
     *
     * @see com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration#beanPostProcessor()
     * 对所有的spring bean进行空值检查
     */
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        //获取所有加载的bean
        String[] beans = applicationContext.getBeanDefinitionNames();
        try {
            for (String beanName : beans) {
                Class<?> beanType = applicationContext.getType(beanName);
                Object bean = applicationContext.getBean(beanName);
                Field[] fields = beanType.getDeclaredFields();
                for (Field field : fields) {
                    Reference reference = field.getAnnotation(Reference.class);
                    if (reference != null) {
                        field.setAccessible(true);
                        Object obj = field.get(bean);
                        //如果dubbo reference已经初始化，则直接返回
                        if (null != obj) {
                            return;
                        }
                        ReferenceBean<?> referenceBean = new ReferenceBean<>(reference);
                        Class<?> interfaceClass = referenceBean.getInterfaceClass();
                        String group = referenceBean.getGroup();
                        String version = referenceBean.getVersion();
                        ClassIdBean classIdBean = new ClassIdBean(interfaceClass, group, version);
                        Object dubboReference =
                                DubboConsumerAutoConfiguration.getDubboReference(classIdBean);
                        if (dubboReference == null) {
                            synchronized (this) {
                                // double check
                                dubboReference =
                                        DubboConsumerAutoConfiguration.getDubboReference(classIdBean);
                                if (dubboReference == null) {
                                    referenceBean.afterPropertiesSet();
                                    // dubboReference should not be null, otherwise it will cause
                                    // NullPointerException
                                    dubboReference = referenceBean.getObject();
                                    // 这里省掉了将这个dubboReference存进DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP里面的操作
                                }
                            }
                        }
                        field.setAccessible(true);
                        field.set(bean, dubboReference);
                    }
                }
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            throw new RuntimeException("load dubbo reference field error!", ex);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
